package de.voegel.controller;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Vector;

import javax.security.sasl.AuthenticationException;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

import org.apache.log4j.Logger;

import de.voegel.model.DbString;
import de.voegel.model.GuiModel;
import de.voegel.model.SaveBird;
import de.voegel.model.entity.BirdEntity;
import de.voegel.model.entity.ChecklistEntity;
import de.voegel.model.entity.CompareBird;
import de.voegel.view.LoadingScreen;
import de.voegel.view.Login;
import de.voegel.view.Main;

public class MainControl extends Observable {

	private Logger logger = null;

	private GuiModel mainModel;
	private DbController dbController;

	// Views
	private Main mainView;
	private Login login;

	public MainControl(Logger logger) {
		try {
			this.logger = logger;
			init();
		} catch (Exception e) {
			aceException(e);
		} finally {
			if (dbController != null) {
				dbController.closeConnection();
			}
		}
	}

	private void getInitialModelData() {
		try {
			getMainModel().laenderListe = dbController.getContryFromDb();
			getMainModel().regionListe = dbController.getRegionFromDb();
			getMainModel().ortListe = dbController.getOrtFromDb();
			getMainModel().zooRegionListe = dbController.getZooRegionFromDb();
			getMainModel().birdList = dbController.getAvailableBirds();

			//
			getMainModel().userList = dbController.getAvailableUser();
			getMainModel().places = dbController.getAvailablePlaces();
		} catch (SQLException e) {
			logger.error(String.format(
					"Fehler beim Initialisieren der Listen %s Details: %s",
					e.getMessage(), appendException(e)));
		}
	}

	private String appendException(Exception e) {
		final Writer result = new StringWriter();
		final PrintWriter printWriter = new PrintWriter(result);
		e.printStackTrace(printWriter);
		return result.toString();
	}

	private void init() throws SQLException {
		this.mainModel = new GuiModel();
		this.dbController = new DbController(this);
		login = new Login(this);

		registerObeserver(login);
	}

	private void registerObeserver(Observer observer) {
		addObserver(observer);
	}

	/**
	 * Benutzer authentifizieren
	 * 
	 * @param user
	 * @param pw
	 * @param dbPasswordText
	 * @param dbUsername
	 * @throws AuthenticationException
	 */
	public void auth(String user, String pw, String dbUsername,
			String dbPasswordText, boolean init) throws AuthenticationException {
		invokeLoading(user, pw, dbUsername, dbPasswordText, init);
	}

	private void invokeLoading(final String user, final String pw,
			final String dbUsername, final String dbPasswordText,
			final boolean init) throws AuthenticationException {
		login.setVisible(false);
		login.dispose();
		deleteObserver(login);

		final LoadingScreen loader = new LoadingScreen();
		loader.setVisible(true);

		Runnable runnable = new Runnable() {
			boolean auth = false;

			@Override
			public void run() {
				// do loading stuff in here
				// for now, simulate loading task with Thread.sleep(...)
				try {
					dbController.setCredentials(dbUsername, dbPasswordText);
					try {
						if (init) {
							System.out.println("Initialisiere Daten");
							loader.setText("Initialisiere Daten");
							dbController.initDB();
						}
						System.out.println("Authentifiziere Benutzer");
						loader.setText("Authentifiziere Benutzer");
						auth = dbController.auth(user, pw);
					} catch (SQLException e) {
						loader.setText(String.format("Fehler: %s",
								e.getMessage()));
						logger.error(String.format("Fehler: %s", e.getMessage()));
					}
					if (auth) {
						mainModel.setUser(user, pw);
					} else {
						throw new AuthenticationException(
								String.format(
										"Benutzer: %s konnte nicht authentifiziert werden",
										user));
					}
					//
					System.out.println("Lade Initialdaten aus DB");
					loader.setText("Lade Initialdaten aus DB");
					updateData();
					loader.setText("--> Fertig");
				} catch (AuthenticationException e) {
					loader.setText(String.format("Fehler: %s", e.getMessage()));
				} finally {
					SwingUtilities.invokeLater(new Runnable() {
						@Override
						public void run() {
							try {
								Thread.sleep(1500);
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
							loader.setVisible(false);
							if (auth) {
								mainView = new Main(MainControl.this);
								registerObeserver(mainView);
								mainView.refreshTree(getMainModel().birdList,
										getMainModel().userList,
										getMainModel().places);
							} else {
								login = new Login(MainControl.this);
								registerObeserver(login);
							}
						}
					});
				}

			}
		};
		new Thread(runnable).start();
	}

	public void cancel() {
		System.exit(0);
	}

	public GuiModel getMainModel() {
		return mainModel;
	}

	public void aceException(Exception e) {
		JOptionPane.showMessageDialog(null,
				String.format("Exception: %s Details: %s", e.getMessage(),
						appendException(e)));
		logger.error(String.format("Exception: %s Details: %s", e.getMessage(),
				appendException(e)));
	}

	public void aceSmallException(Exception e) {
		JOptionPane
				.showMessageDialog(null, String.format("%s", e.getMessage()));
		logger.error(String.format("Exception: %s", e.getMessage()));
	}

	public void showMessageDialog(String text) {
		JOptionPane.showMessageDialog(null, String.format("%s", text));
	}

	public Logger getLogger() {
		return logger;
	}

	public boolean getUserIsAdmin() {
		if (getMainModel().getUser().getUser() != null) {
			return dbController.userIsAdmin(getMainModel().getUser().getUser());
		} else {
			return false;
		}
	}

	public boolean addNewOrt(String ort, String land, String region,
			String gebiet) throws Exception {
		return dbController.addNewOrt(ort, land, region, gebiet) != 0;
	}

	public String addNewBeobachtung(ArrayList<SaveBird> saveBirds,
			java.sql.Timestamp start, java.sql.Timestamp stop, String ort,
			String land, String region, String gebiet, String bemerkung)
			throws Exception {
		String user = mainModel.getUser().getUser();

		return dbController.addNewBeobachtung(saveBirds, start, stop,
				bemerkung, user, ort, land, region, gebiet);
	}

	public List<ChecklistEntity> getChecklist(String country, boolean self) {
		String user = null;
		if (self) {
			user = mainModel.getUser().getUser();
		}

		try {
			return dbController.getChecklist(country, user);
		} catch (SQLException e) {
			aceException(e);
		}
		return null;
	}

	public Vector<BirdEntity> getAvailableSubspecies(String bird)
			throws Exception {
		return dbController.getAvailableSubspecies(bird);
	}

	public void updateData() {
		getInitialModelData();
	}

	public HashSet<DbString> getAvailableCountry(String selString) {
		try {
			getMainModel().laenderListe = dbController
					.getAvailableCountry(selString);
		} catch (SQLException e) {
			aceException(e);
		}
		return getMainModel().laenderListe;
	}

	public HashSet<DbString> getAvailableOrt(String selString) {
		try {
			getMainModel().ortListe = dbController.getAvailableOrt(selString);
		} catch (SQLException e) {
			aceException(e);
		}
		return getMainModel().ortListe;
	}

	public HashSet<DbString> getAvailableName(String selString) {
		try {
			getMainModel().regionListe = dbController
					.getAvailableName(selString);
		} catch (SQLException e) {
			aceException(e);
		}
		return getMainModel().regionListe;
	}

	public HashSet<CompareBird> getBirdsDe() {
		HashSet<CompareBird> res = new HashSet<>();
		try {
			res.addAll(dbController.getBirdsDe());
		} catch (SQLException e) {
			aceException(e);
		}
		return res;
	}

	public HashSet<CompareBird> getCompareBirds() {
		HashSet<CompareBird> res = new HashSet<>();
		try {
			res.addAll(dbController.getGermanBirds(getMainModel().getUser()
					.getUser()));
		} catch (SQLException e) {
			aceException(e);
		}
		return res;
	}

	public void addBird(String latName, String dName, String engName,
			String spec) {
		try {
			dbController.addBird(latName, dName, engName, spec);
			showMessageDialog("Daten eingepflegt");
		} catch (SQLException e) {
			aceException(e);
		}
	}

}
